/* usb.c -- AT91SAM7 usb core
**
** Copyright 2006, Brian Swetland.  All rights reserved.
** See provided LICENSE file or http://frotz.net/LICENSE for details.
*/

#include "at91sam7s.h"
#include "devices.h"
#include "usb.h"

#define TYPE_DEVICE          1
#define TYPE_CONFIGURATION   2
#define TYPE_STRING          3
#define TYPE_INTERFACE       4
#define TYPE_ENDPOINT        5

#if 0
#define TRACE(x...) dprintf(x)
#else
#define TRACE(x...)
#endif

#ifndef USB_VENDOR
#error USB_VENDOR definition required
#undef USB_VENDOR
#define USB_VENDOR 0
#endif

#ifndef USB_PRODUCT
#error USB_PRODUCT definition required
#undef USB_PRODUCT
#define USB_PRODUCT 0
#endif

unsigned char device_dscr[] = {
    18,              // length
    TYPE_DEVICE,     // type
    0x10, 0x01,      // usb spec
    0x00,            // class
    0x00,            // subclass
    0x00,            // protocol
    0x08,            // max packet size
    ((USB_VENDOR) & 0xff), (((USB_VENDOR) >> 8) & 0xff),
    ((USB_PRODUCT) & 0xff), (((USB_PRODUCT) >> 8) & 0xff),
    0x00, 0x01,      // version 1.0
    0x00,            // manufacturer str idx
    0x00,            // product str idx
    0x00,            // serial number index
    0x01,            // number of configs,
};

unsigned char config_dscr[] = {
    0x09,            // length
    TYPE_CONFIGURATION,
    0x20, 0x00,      // total length
    0x01,            // # interfaces
    0x01,            // config value
    0x00,            // config string
    0x80,            // attributes
    0x80,            // XXX max power (250ma)

    0x09,            // length
    TYPE_INTERFACE,
    0x00,            // interface number
    0x00,            // alt number
    0x02,            // # endpoints
    0xff,            // class
    0xff,            // subclass
    0xff,            // protocol
    0x00,            // interface string

    0x07,            // length
    TYPE_ENDPOINT,
    0x81,            // in, #1
    0x02,            // bulk
    0x40, 0x00,      // max packet 64
    0x00,            // interval
    
    0x07,            // length
    TYPE_ENDPOINT,
    0x03,            // out, #3
    0x02,            // bulk
    0x40, 0x00,      // max packet 64
    0x00,            // interval
};


typedef struct 
{
        /* the control endpoint is only 8 bytes deep,
           so we need to track our state across multiple
           IN packet transactions */
    unsigned char *ctl_out_data;
    unsigned ctl_out_count;

        /* when ack'ing a SET_ADDRESS, we need to cache
           the new address to apply it once the transaction
           is complete */
    unsigned new_address;

    unsigned char recv[64];
    unsigned char xmit[64];
} usb_info;


static usb_info USB = {
    .ctl_out_data = 0,
    .ctl_out_count = 0,
    .new_address = 0,
};

static void reset_endpoints(usb_info *usb, AT91UDP *udp)
{
        /* reset endpoints */
    udp->RST_EP = UDP_EP0 | UDP_EP1 | UDP_EP2 | UDP_EP3;
    udp->RST_EP = 0;

    usb->ctl_out_count = 0;
    usb->new_address = 0;
    
    udp->CSR0 = UDP_TYPE_CONTROL | UDP_EPEDS;
    
        /* disable non-control endpoints */
    udp->CSR1 = 0;
    udp->CSR2 = 0;
    udp->CSR3 = 0;
}


/* PLL must be configured to provide 48MHz USBCLK */
void usb_init()
{
    AT91UDP *udp = AT91UDP_ADDR;
    AT91PMC *pmc = AT91PMC_ADDR;
    usb_info *usb = &USB;
    
        /* enable the usb bus and peripheral clocks */
    pmc->SCER = PMC_UDP;
    pmc->PCER = (1 << PID_UDP);
    
        /* unconfigured, address = 0, function disabled */
    udp->GLB_STAT = 0; 
    udp->FADDR = 0; 

    reset_endpoints(usb, udp);
    
        /* enable transciever */
    udp->TXVC = 0;
    udp->IDR = 0xffffffff;
    udp->IER = (UDP_ENDBUSRES | UDP_EP0INT | UDP_EP1INT | UDP_EP3INT);

#if 0
    pio_set(PIN_USB_PULLUP, 0);
    for(n = 0; n < 100; n++);
    pio_set(PIN_USB_PULLUP, 1);
#endif
}

static inline void ctl_ack(AT91UDP *udp)
{
    udp->CSR0 |= UDP_TXPKTRDY;
}

static inline void ctl_stall(AT91UDP *udp)
{
    udp->CSR0 |= UDP_FORCESTALL;
}

static void ctl_send_packet(usb_info *usb, AT91UDP *udp)
{
    unsigned len = usb->ctl_out_count > 8 ? 8 : usb->ctl_out_count;
    
    usb->ctl_out_count -= len;
    while(len--) udp->FDR0 = *usb->ctl_out_data++;
    udp->CSR0 |= UDP_TXPKTRDY;
}

static void ctl_send(usb_info *usb, AT91UDP *udp,
                     unsigned char *data, unsigned len, unsigned reqlen)
{
    if(len > reqlen) len = reqlen;
    if(len > 64) len = 64;

    usb->ctl_out_data = data;
    usb->ctl_out_count = len;

    ctl_send_packet(usb, udp);
}

typedef struct 
{
    unsigned char type;
    unsigned char request;
    unsigned short value;
    unsigned short index;
    unsigned short length;
} __attribute__ ((packed)) setup_msg;

#define STD_SET_ADDRESS               0x0500
#define STD_GET_DESCRIPTOR            0x0680
#define STD_GET_CONFIGURATION         0x0880
#define STD_SET_CONFIGURATION         0x0900
#define STD_CLEAR_FEATURE_ENDPOINT    0x0102
#define STD_SET_INTERFACE             0x0B01

/* There's plenty of braindamage here...
**
** These bits are *cleared* by writing *0* to them (what the hell):
** TXCOMP, RX_DATA_BK0, RXSETUP, STALLSENT, RX_DATA_BK1
**
** These bits are read/write by the driver and not modified
** by the hardware.  Safe for RMW access:
** FORCESTALL, DIR, EPTYPE, EPEDS
**
** These bits are setable by driver and cleared by hardware:
** TXPKTRDY
**
** These bits are read-only:
** DTGLE, RXBYTECNT
**
*/

#define ZERO_CLEAR_BITS (UDP_TXCOMP | UDP_RXSETUP | UDP_STALLSENT | UDP_RX_DATA_BK0 | UDP_RX_DATA_BK1)

#define ACK_EVENT(reg, bit) (reg = ((reg | ZERO_CLEAR_BITS) & (~(bit))))

static void usb_ep0(usb_info *usb, AT91UDP *udp)
{
    unsigned n, i;
    setup_msg setup;
    
    n = udp->CSR0;

    if(n & UDP_RX_DATA_BK1){
        TRACE("DATA_BK1 (%d)\n", (n >> 16) & 0x3ff);
        return;
    }
    if(n & UDP_RX_DATA_BK0){
        ACK_EVENT(udp->CSR0, UDP_RX_DATA_BK0);
        TRACE("DATA_BK0 (%d)\n", (n >> 16) & 0x3ff);
        return;
    }
    if(n & UDP_TXCOMP){
        ACK_EVENT(udp->CSR0, UDP_TXCOMP);
        while((udp->CSR0 & UDP_TXCOMP));
        if(usb->new_address){
            udp->FADDR = UDP_FEN | usb->new_address; // BUG overflow?
            udp->GLB_STAT = UDP_FADDEN; 
            TRACE("NEW_ADDRESS %d\n", usb->new_address);
            usb->new_address = 0;
        }
        if(usb->ctl_out_count) {
            ctl_send_packet(usb, udp);
        }
        return;
    }
    if(n & UDP_STALLSENT){
        ACK_EVENT(udp->CSR0, UDP_STALLSENT | UDP_FORCESTALL);
        while((udp->CSR0 & (UDP_STALLSENT | UDP_FORCESTALL)));
        TRACE("STALLSENT\n");
        return;
    }
    if(n & UDP_RXSETUP){
        unsigned char *x = (unsigned char*) &setup;
        for(i = 0; i < 8; i++) *x++ = udp->FDR0;

        if(setup.type & 0x80) {
            udp->CSR0 |= UDP_DIR;
            while(!(udp->CSR0 & UDP_DIR));  // BUG race?
        }

        ACK_EVENT(udp->CSR0, UDP_RXSETUP);
        while((udp->CSR0 & UDP_RXSETUP)); // BUG race?
        
        switch((setup.request << 8) | setup.type){
        case STD_SET_ADDRESS:
            if((setup.value == 0) || (setup.value > 127)) {
                ctl_stall(udp);
            } else {
                ctl_ack(udp);
                usb->new_address = setup.value;
            }
            TRACE("SET_ADDRESS %d\n", setup.value);
            break;

        case STD_SET_CONFIGURATION:
            ctl_ack(udp);
            if(setup.value) {
                udp->GLB_STAT = UDP_CONFG;
                udp->RST_EP = UDP_EP1 | UDP_EP3;
                udp->RST_EP = 0;
                udp->CSR1 = UDP_TYPE_BULK_IN | UDP_EPEDS;
                udp->CSR3 = UDP_TYPE_BULK_OUT | UDP_EPEDS;
                usb_status(1);
            } else {
                udp->GLB_STAT = UDP_FADDEN;
                udp->CSR1 = 0;
                udp->CSR3 = 0;
                usb_status(0);
            }
            TRACE("SET_CONFIGURATION %d\n", setup.value);
            break;

        case STD_SET_INTERFACE:
            if((setup.value != 0) || (setup.index != 0)){
                ctl_stall(udp);
            } else {
                udp->RST_EP = UDP_EP1 | UDP_EP3;
                udp->RST_EP = 0;
                ctl_ack(udp);
            }
            TRACE("SET_INTERFACE val: %h idx: %h\n", setup.value, setup.index);
            break;
            
        case STD_GET_DESCRIPTOR:
            TRACE("GET_DESCRIPTOR %h\n",setup.value);
            if(setup.value == 0x100) {
                ctl_send(usb, udp, device_dscr, sizeof(device_dscr), setup.length);
            } else if(setup.value == 0x200) {
                ctl_send(usb, udp, config_dscr, sizeof(config_dscr), setup.length);
            } else {
                ctl_stall(udp);
            }
            break;

        default:
            TRACE("SETUP %b %b %h %h %h UNSUPPORTED (%h)\n",
                  setup.type, setup.request,
                  setup.value, setup.index, setup.length,
                  (setup.request << 8) | setup.type);
            
            ctl_stall(udp);
            return;
        }
        return;
    }
}

static void usb_bulk_io(usb_info *usb, AT91UDP *udp)
{
    unsigned n;
    unsigned char *x;
    
    if(udp->CSR1 & UDP_TXPKTRDY) {
        return;
    }

#if WITH_FASTER_USB
    n = usb_bulk_send(&x);
#else
    n = usb_bulk_send(x = usb->xmit);
#endif
    if(n == 0) return;
    
    while(n-- > 0) {
        udp->FDR1 = *x++;
    }
    udp->CSR1 |= UDP_TXPKTRDY;
}

void usb_send_ready(void)
{
    AT91UDP *udp = AT91UDP_ADDR;
    usb_info *usb = &USB;
    
    usb_bulk_io(usb, udp);
}

static void usb_ep1(usb_info *usb, AT91UDP *udp)
{
    unsigned n = udp->CSR1;
    
    if(n & UDP_TXCOMP) {
        ACK_EVENT(udp->CSR1, UDP_TXCOMP);
        usb_bulk_io(usb, udp);
    }
}

static void usb_ep3(usb_info *usb, AT91UDP *udp)
{
    unsigned n,i,len;
    unsigned char *x = usb->recv;
    
    n = udp->CSR3;

    if(n & UDP_RX_DATA_BK0) {
        len = (n >> 16) & 0x3ff;
        for(i = 0; i < len; i++) *x++ = udp->FDR3;
        ACK_EVENT(udp->CSR3, UDP_RX_DATA_BK0);
        TRACE("EP2> DATA_BK0 (%d) CSR3 %x > %x\n", len, n, udp->CSR3);
        if(usb_bulk_recv(usb->recv, len)) {
            usb_bulk_io(usb, udp);
        }
    }
}

void usb_poll()
{
    AT91UDP *udp = AT91UDP_ADDR;
    usb_info *usb = &USB;
    unsigned n;

        /* ignore SOF, suspend, resume, wake, etc */
    for(;;) {
//        dprintf("usb_poll() ISR = %x\n",udp->ISR);
        n = udp->ISR & (UDP_ENDBUSRES | UDP_EP0INT | UDP_EP1INT | UDP_EP3INT);
        if(n == 0) return;
    

        if(n & UDP_EP1INT){
            usb_ep1(usb, udp);
            continue;
        }
        if(n & UDP_EP3INT){
            usb_ep3(usb, udp);
            continue;
        }

            /* we handle ep0 *after* the data endpoints because
               we want to make sure we empty the data FIFOs before
               doing something like resetting the data toggle
               with a SET_INTERFACE request (as linux does when it
               closes a usb device).
            */
        if(n & UDP_EP0INT){
            usb_ep0(usb, udp);
            continue;
        }
        if(n & UDP_ENDBUSRES) {
                /* done with reset, enable the part for io */
            reset_endpoints(usb, udp);
            udp->FADDR = UDP_FEN;
            udp->ICR = UDP_ENDBUSRES;
            udp->IDR = 0xffffffff;
            udp->IER = (UDP_ENDBUSRES | UDP_EP0INT | UDP_EP1INT | UDP_EP3INT);
            continue;
        }
    }

}
